use std::{collections::HashMap, error::Error, fs::read_to_string};

use regex::Regex;

#[derive(Debug)]
struct Iderr;
impl<T: Error> From<T> for Iderr {
    fn from(_value: T) -> Self {
        Self
    }
}

struct Area(HashMap<String, String>);
impl Area {
    /// read data from a local file
    fn build() -> Self {
        let mut map = HashMap::new();
        for line in read_to_string("../../topic1/solutiont7/code")
            .unwrap()
            .lines()
        {
            let mut it = line.split(" ");
            let code = it.next().unwrap();
            let name = it.next().unwrap();
            map.insert(code.to_owned(), name.to_owned());
        }
        Self(map)
    }
    fn search(&self, code: &str) -> Result<String, Iderr> {
        let s = match self.0.get(code) {
            Some(s) => s,
            None => return Err(Iderr),
        };
        // use regex for format (insert '-')
        let reg = Regex::new(r".*?[县市区州省]").unwrap();
        let mut ans = String::new();
        let capts = reg.captures_iter(s).collect::<Vec<_>>();
        let capt_len = capts.len();
        let mut it = capts.iter();
        ans += it.next().unwrap().get(0).unwrap().as_str();
        ans += "-";
        // if only has two parts, insert "市辖区" as the second part
        ans += if capt_len == 2 {
            "市辖区"
        } else if capt_len == 3 {
            it.next().unwrap().get(0).unwrap().as_str()
        } else {
            return Ok(s.to_owned());
        };
        ans += "-";
        ans += it.next().unwrap().get(0).unwrap().as_str();
        Ok(ans)
    }
}

pub fn check_id_card(id_card_no: &str) -> String {
    match parse(id_card_no) {
        Ok(s) => s,
        _ => "身份证号码错误".to_owned(),
    }
}

fn parse(id: &str) -> Result<String, Iderr> {
    // length check
    if id.len() != 18 && id.len() != 15 {
        return Err(Iderr);
    }
    // checksum check (the last digit)
    if id.len() == 18 {
        check(id)?;
    }

    let (district, birth, sex) = if id.len() == 18 {
        (&id[0..6], &id[6..14], id.chars().nth(16).unwrap())
    } else {
        (&id[0..6], &id[6..12], id.chars().last().unwrap())
    };

    let area = Area::build();
    let area = area.search(district)?;

    let sex = if sex as u32 % 2 == 0 { "女" } else { "男" };
    let (year, month, day) = check_birth(birth)?;

    Ok(format!(
        "身份证号码正确,{},{}年{:02}月{:02}日,{}",
        sex, year, month, day, area
    ))
}

/// check and return the birthday information
fn check_birth(mut id: &str) -> Result<(usize, usize, usize), Iderr> {
    let year: usize = if id.len() == 8 {
        let res = id[0..4].parse()?;
        id = &id[4..];
        res
    } else {
        let res = id[0..2].parse::<usize>()? + 1900;
        id = &id[2..];
        res
    };

    let month: usize = id[0..2].parse()?;
    let day: usize = id[2..4].parse()?;
    if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
        return Err(Iderr);
    }
    const DAY_OF_MONTH: [usize; 13] = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    if is_leap_year(year) && month == 2 && day == 29 {
        return Ok((year, month, day));
    }
    if day > DAY_OF_MONTH[month] {
        return Err(Iderr);
    }
    Ok((year, month, day))
}

fn is_leap_year(y: usize) -> bool {
    if y % 400 == 0 {
        true
    } else if y % 100 == 0 {
        false
    } else {
        y % 4 == 0
    }
}

/// whether the last digit is correct
fn check(id: &str) -> Result<(), Iderr> {
    let mut chk = 0;
    for (i, c) in id.chars().take(17).enumerate() {
        chk += (1 << (17 - i)) % 11 * (c as usize - 48);
    }

    chk %= 11;
    const CKMAP: [char; 11] = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
    if CKMAP[chk] == id.chars().nth(17).unwrap() {
        Ok(())
    } else {
        Err(Iderr)
    }
}
